﻿/*
 * THIS CLASS IS BASED ON 'FORMMAIN.CS' FROM THE 'SOUND CATCHER' APPLICATION,
 * DETAILS OF WHICH ARE AS FOLLOWS:
 */ 
 
/* Copyright (C) 2008 Jeff Morton (jeffrey.raymond.morton@gmail.com)
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
 
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */


using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace RefOffPDA
{
    public partial class WhistleDetector
    {
        private WaveInRecorder _recorder;
        private byte[] _recorderBuffer;
        private WaveOutPlayer _player;
        private byte[] _playerBuffer;
        private WaveFormat _waveFormat;
        private AudioFrame _audioFrame;
        private FifoStream _streamOut;
        private MemoryStream _streamMemory;
        private Stream _streamWave;
        private FileStream _streamFile;
        private bool _isPlayer = false;  // audio output for testing
        private bool _isTest = false;  // signal generation for testing
        private bool _isSaving = false;
        //MODIFICATION: Following line commented out - no longer required
        //private bool _isShown = true;
        private string _sampleFilename;
        private DateTime _timeLastDetection;

        //MODIFICATION: Following variables added to class. In the original desktop
        //              application, these were stored in a settings file, but it was
        //              not possible to use this file in a mobile device application
        //              so the required settings are stored here as variables instead
        private bool SettingIsDetectingEvents = true;
        private bool SettingIsSaving = false;
        private String SettingOutputPath = "";
        private int SettingSecondsToSave = 3;
        private int SettingAmplitudeThreshold = 30000;
        private int SettingSamplesPerSecond = 44100;
        private int SettingBytesPerFrame = 8192;
        private int SettingBitsPerSample = 16;
        private int SettingChannels = 2;
        private int SettingAudioInputDevice = 0;
        private int SettingAudioOutputDevice = -1;

        public WhistleDetector()
        {
            
        }

        //MODIFICATION: FormMain_Load method removed - no longer required,
        //              replaced by begin() method below

        //MODIFICATION: FormMain_Resize method removed - no longer required

        //MODIFICATION: FormMain_SizeChanged method removed - no longer required

        //MODIFICATION: FormMain_Closing method removed - no longer required,
        //              replaced by end() method below
        
        //MODIFICATION: notifyIcon1_MouseDoubleClick method removed - no longer required

        //MODIFICATION: aboutToolStripMenuItem_Click method removed - no longer required

        //MODIFICATION: exitToolStripMenuItem_Click method removed - no longer required

        //MODIFICATION: optionsToolStripMenuItem_Click method removed - no longer required

        //MODIFICATION: settingsToolStripMenuItem_Click method removed - no longer required
        
        //MODIFICATION: Start() method not modified
        private void Start()
        {
            Stop();
            try
            {
                _waveFormat = new WaveFormat(SettingSamplesPerSecond, SettingBitsPerSample, SettingChannels);
                _recorder = new WaveInRecorder(SettingAudioInputDevice, _waveFormat, SettingBytesPerFrame * SettingChannels, 3, new BufferDoneEventHandler(DataArrived));
                if (_isPlayer == true)
                    _player = new WaveOutPlayer(SettingAudioOutputDevice, _waveFormat, SettingBytesPerFrame * SettingChannels, 3, new BufferFillEventHandler(Filler));
                //textBoxConsole.Text += DateTime.Now.ToString() + " : Audio input device polling started\r\n";
                //textBoxConsole.Text += DateTime.Now + " : Device = " + SettingAudioInputDevice.ToString() + "\r\n";
                //textBoxConsole.Text += DateTime.Now + " : Channels = " + SettingChannels.ToString() + "\r\n";
                //textBoxConsole.Text += DateTime.Now + " : Bits per sample = " + SettingBitsPerSample.ToString() + "\r\n";
                //textBoxConsole.Text += DateTime.Now + " : Samples per second = " + SettingSamplesPerSecond.ToString() + "\r\n";
                //textBoxConsole.Text += DateTime.Now + " : Frame size = " + SettingBytesPerFrame.ToString() + "\r\n";
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.InnerException.ToString());
            }
        }

        //MODIFICATION: Stop() method not modified
        private void Stop()
        {
            if (_recorder != null)
                try
                {
                    _recorder.Dispose();
                }
                finally
                {
                    _recorder = null;
                }
            if (_isPlayer == true)
            {
                if (_player != null)
                    try
                    {
                        _player.Dispose();
                    }
                    finally
                    {
                        _player = null;
                    }
                _streamOut.Flush(); // clear all pending data
            }
            //textBoxConsole.Text += DateTime.Now.ToString() + " : Audio input device polling stopped\r\n";
        }

        //MODIFICATION: Filler() method not modified
        private void Filler(IntPtr data, int size)
        {
            if (_isPlayer == true)
            {
                if (_playerBuffer == null || _playerBuffer.Length < size)
                    _playerBuffer = new byte[size];
                if (_streamOut.Length >= size)
                    _streamOut.Read(_playerBuffer, 0, size);
                else
                    for (int i = 0; i < _playerBuffer.Length; i++)
                        _playerBuffer[i] = 0;
                System.Runtime.InteropServices.Marshal.Copy(_playerBuffer, 0, data, size);
            }
        }

        //MODIFICATION: Comments added to DataArrived() method below

        //This method is used to analyse audio whenever the microphone
        //on the device detects some sound input
        private void DataArrived(IntPtr data, int size)
        {       
            //Determine if input is to be saved, which in RefOff it is not
            if (_isSaving == true)
            {
                byte[] recBuffer = new byte[size];
                System.Runtime.InteropServices.Marshal.Copy(data, recBuffer, 0, size);
                _streamMemory.Write(recBuffer, 0, recBuffer.Length);
            }

            //Determine if the input buffer is empty or not full, and if so create a new one
            if (_recorderBuffer == null || _recorderBuffer.Length != size)
                _recorderBuffer = new byte[size];

            //If the input buffer is not empty, process it
            if (_recorderBuffer != null)
            {
                //Copy the contents of the buffer from unmanaged memory to the array of bytes
                System.Runtime.InteropServices.Marshal.Copy(data, _recorderBuffer, 0, size);

                //Determine if the application needs to play audio, which RefOff does not
                if (_isPlayer == true)
                    _streamOut.Write(_recorderBuffer, 0, _recorderBuffer.Length);

                //Call the Process() method in the AudioFrame class, which was not modified
                _audioFrame.Process(ref _recorderBuffer);

                //The above Process() method sets a flag if the specified
                //amplitude is exceeded, i.e. if a whistle has been detected
                if (_audioFrame.IsEventActive == true)
                {
                    //Following code run if audio input needs to be saved, which in RefOff
                    //it does not
                    if (_isSaving == false && SettingIsSaving == true)
                    {
                        _sampleFilename = DateTime.Now.ToString("yyyyMMddHHmmss") + ".wav";
                        _timeLastDetection = DateTime.Now;
                        _isSaving = true;
                    }
                    //This is where whistle event code would be inserted
                    else
                    {
                        //MODIFICATION: Following line removed - no longer required
                        //_timeLastDetection = DateTime.Now; 
                    }
                    
                    //MODIFICATION: Following lines removed, no longer required
                    //Invoke(new MethodInvoker(AmplitudeEvent));

                    //MODIFICATION: Following line added to called AmplitudeEvent() method
                    //AmplitudeEvent();
                }

                //The following section of code is never run as RefOff does not need
                //to save data
                if (_isSaving == true && DateTime.Now.Subtract(_timeLastDetection).Seconds > SettingSecondsToSave)
                {
                    byte[] waveBuffer = new byte[SettingBitsPerSample];
                    _streamWave = WaveStream.CreateStream(_streamMemory, _waveFormat);
                    waveBuffer = new byte[_streamWave.Length - _streamWave.Position];
                    _streamWave.Read(waveBuffer, 0, waveBuffer.Length);

                    if (SettingOutputPath != "")
                        _streamFile = new FileStream(SettingOutputPath + "\\" + _sampleFilename, FileMode.Create);
                    else
                        _streamFile = new FileStream(_sampleFilename, FileMode.Create);
                    _streamFile.Write(waveBuffer, 0, waveBuffer.Length);

                    if (_streamWave != null) { _streamWave.Close(); }

                    if (_streamFile != null) { _streamFile.Close(); }
                    _streamMemory = new MemoryStream();
                    _isSaving = false;

                    //MODIFICATION: Following line removed - no longer required
                    //Invoke(new MethodInvoker(FileSavedEvent));
                }

                //MODIFICATION: Following lines removed - no longer required
                //_audioFrame.RenderTimeDomainLeft(ref pictureBoxTimeDomainLeft);
                //_audioFrame.RenderTimeDomainRight(ref pictureBoxTimeDomainRight);
                //_audioFrame.RenderFrequencyDomainLeft(ref pictureBoxFrequencyDomainLeft, Properties.Settings.Default.SettingSamplesPerSecond);
                //_audioFrame.RenderFrequencyDomainRight(ref pictureBoxFrequencyDomainRight, Properties.Settings.Default.SettingSamplesPerSecond);
                //_audioFrame.RenderSpectrogramLeft(ref pictureBoxSpectrogramLeft);
                //_audioFrame.RenderSpectrogramRight(ref pictureBoxSpectrogramRight);
            }
        }

        //This method is called when the specified amplitude threshold is exceeded, i.e.
        //when a whistle is detected by the PDA device microphone
        private void AmplitudeEvent()
        {
            //MODIFICATION: Following line removed - no longer required
            //toolStripStatusLabel1.Text = "Last event: " + _timeLastDetection.ToString();

            //Code would be inserted here to record the current GPS location
            //of the PDA device, and store it in the temporary variable, ready
            //to be inserted into the database when the next match incident
            //takes place
        }
        
        //MODIFICATION: FileSavedEvent() method removed - no longer required

        //MODIFICATION: menuItem1_Click method removed - no longer required

        //MODIFICATION: begin() method added - this is based on the FormMain_Load method
        //Method is used to start whistle detection
        public void begin()
        {
            //Check that there is a microphone present
            if (WaveNative.waveInGetNumDevs() == 0)
            {
                //MODIFICATION: Following lines added
                //Inform the user if no devices detected
                MessageBox.Show("No audio input devices detected");

                //MODIFICATION: Following line removed
                //textBoxConsole.Text += DateTime.Now.ToString() + " : No audio input devices detected\r\n";
            }
            else
            {
                //MODIFICATION: Following line removed
                //textBoxConsole.Text += DateTime.Now.ToString() + " : Audio input device detected\r\n";
                
                //MODIFICATION: Comments below added
                
                //Determine if the device is being used to play audio back, and
                //if so create a new audio stream to play sound. Note: RefOff is
                //not used to play audio
                if (_isPlayer == true)
                    _streamOut = new FifoStream();

                //Create a new AudioFrame, determining whether the application is
                //running in test mode or not. Note: RefOff is not running in test mode
                _audioFrame = new AudioFrame(_isTest);

                //Specify whether the AudioFrame should be examined for amplitude events,
                //which in the case of RefOff it should be
                _audioFrame.IsDetectingEvents = SettingIsDetectingEvents;

                //Specify the amplitude threshold value, over which an event is fired, i.e.
                //the volume which is deemed to represent the sound of a whistle
                _audioFrame.AmplitudeThreshold = SettingAmplitudeThreshold;

                //Create a new MemoryStream for saving data if necessary
                _streamMemory = new MemoryStream();

                //Call the Start() method to start listening for audio
                Start();
            }
        }

        //MODIFICATION: end() method added - this is based on the FormMain_Closing method
        //Method is used to stop whistle detection
        public void end()
        {
            //Call the Stop() method
            Stop();

            //Following code not relevant as RefOff does not need to save data
            if (_isSaving == true)
            {
                byte[] waveBuffer = new byte[SettingBitsPerSample];
                _streamWave = WaveStream.CreateStream(_streamMemory, _waveFormat);
                waveBuffer = new byte[_streamWave.Length - _streamWave.Position];
                _streamWave.Read(waveBuffer, 0, waveBuffer.Length);

                //MODIFICATION: Following lines removed - no longer required
                //if (Properties.Settings.Default.SettingOutputPath != "")
                //    _streamFile = new FileStream(Properties.Settings.Default.SettingOutputPath + "\\" + _sampleFilename, FileMode.Create);
                //else
                //    _streamFile = new FileStream(_sampleFilename, FileMode.Create);
                //_streamFile.Write(waveBuffer, 0, waveBuffer.Length);
                //_isSaving = false;
            }

            //Following code is used to flush and clear input
            //and output buffers used during audio processing
            if (_streamOut != null)
                try
                {
                    _streamOut.Close();
                }
                finally
                {
                    _streamOut = null;
                }
            if (_streamWave != null)
                try
                {
                    _streamWave.Close();
                }
                finally
                {
                    _streamWave = null;
                }
            if (_streamFile != null)
                try
                {
                    _streamFile.Close();
                }
                finally
                {
                    _streamFile = null;
                }
            if (_streamMemory != null)
                try
                {
                    _streamMemory.Close();
                }
                finally
                {
                    _streamMemory = null;
                }
        }
    }
}